﻿@using Flagrum.Web.Features.AssetExplorer.Base
@using Flagrum.Web.Features.AssetExplorer.Data
@using Flagrum.Web.Persistence.Entities
@using Flagrum.Core.Archive
@using Flagrum.Core.Ebex.Xmb2
@using Flagrum.Core.Utilities
@using System.IO
@using System.Text
@using System.Collections.Concurrent
@using Flagrum.Core.Gfxbin.Btex
@inject IWpfService WpfService
@inject FlagrumDbContext Context
@inject IStringLocalizer<ExportFolderModal> Localizer
@inject AppStateService AppState
@inject ProfileService Profile

<AutosizeModal @ref="Modal" Padding="4" MinWidth="405px">
    <HeaderView>
        <span class="text-grey-300 flex-grow">@(Parent.CurrentView == AssetExplorerView.GameView ? Localizer["ExportFolder"] : Localizer["ConvertFolder"])</span>
        <span class="material-icons cursor-pointer" @onclick="Close">cancel</span>
    </HeaderView>
    <BodyView>
        <div class="h-full">
            <div class="flex flex-row">
                <div style="flex: 0 0 370px;">
                    @if (Parent.CurrentView == AssetExplorerView.FileSystem)
                    {
                        <label>@Localizer["ConvertFormats"]</label>
                        <div class="row mt-2 mb-4 bg-grey-900 border border-grey-600 p-3">
                            <Checkbox IsChecked="@(ExportContext.ExportFormats["btex"])" OnChange="@(b => { ExportContext.ExportFormats["btex"] = b; StateHasChanged(); })"/>
                            <span class="ml-2 mr-4">btex</span>
                            <Checkbox IsChecked="@(ExportContext.ExportFormats["exml"])" OnChange="@(b => { ExportContext.ExportFormats["exml"] = b; StateHasChanged(); })"/>
                            <span class="ml-2">exml</span>
                        </div>
                    }
                    else
                    {
                        <label>@Localizer["QuickPresets"]</label>
                        <div class="row mt-2 mb-4 bg-grey-900 border border-grey-600 p-3">
                            <Button Text="Textures" OnClick="() => ChangePreset(ExportContextPreset.Textures)" CssClass="mr-3"/>
                            <Button Text="Scripts" OnClick="() => ChangePreset(ExportContextPreset.Scripts)" CssClass="mr-3"/>
                            <Button Text="Models" OnClick="() => ChangePreset(ExportContextPreset.Models)"/>
                        </div>
                    }
                    @if (ExportContext.ExportFormats["btex"])
                    {
                        <label>@Localizer["TextureFormat"]</label>
                        <div class="row mt-2 mb-4 bg-grey-900 border border-grey-600 p-3">
                            <Checkbox IsChecked="@(ExportContext.TextureFormats["png"])" OnChange="@(b => { ExportContext.TextureFormats["png"] = b; StateHasChanged(); })"/>
                            <span class="ml-2 mr-4">png</span>
                            <Checkbox IsChecked="@(ExportContext.TextureFormats["tga"])" OnChange="@(b => { ExportContext.TextureFormats["tga"] = b; StateHasChanged(); })"/>
                            <span class="ml-2 mr-4">tga</span>
                            <Checkbox IsChecked="@(ExportContext.TextureFormats["dds"])" OnChange="@(b => { ExportContext.TextureFormats["dds"] = b; StateHasChanged(); })"/>
                            <span class="ml-2 mr-4">dds</span>
                            @if (Parent.CurrentView == AssetExplorerView.GameView)
                            {
                                <Checkbox IsChecked="@(ExportContext.TextureFormats["btex"])" OnChange="@(b => { ExportContext.TextureFormats["btex"] = b; StateHasChanged(); })"/>
                                <span class="ml-2">btex</span>
                            }
                        </div>
                    }
                    @if (Parent.CurrentView == AssetExplorerView.GameView && ExportContext.ExportFormats["exml"])
                    {
                        <label>@Localizer["ScriptFormat"]</label>
                        <div class="row mt-2 mb-4 bg-grey-900 border border-grey-600 p-3">
                            <Checkbox IsChecked="@(ExportContext.ScriptFormats["xml"])" OnChange="@(b => { ExportContext.ScriptFormats["xml"] = b; StateHasChanged(); })"/>
                            <span class="ml-2 mr-4">xml</span>
                            <Checkbox IsChecked="@(ExportContext.ScriptFormats["exml"])" OnChange="@(b => { ExportContext.ScriptFormats["exml"] = b; StateHasChanged(); })"/>
                            <span class="ml-2">exml</span>
                        </div>
                    }
                    <label>@(Parent.CurrentView == AssetExplorerView.GameView ? Localizer["AdditionalOptions"] : Localizer["AdditionalConversionOptions"])</label>
                    <div class="row mt-2 mb-6 bg-grey-900 border border-grey-600 p-3">
                        <Checkbox IsChecked="@ExportContext.Recursive" OnChange="v => { ExportContext.Recursive = v; StateHasChanged(); }"/>
                        <span class="inline-block ml-2">@(Parent.CurrentView == AssetExplorerView.GameView ? Localizer["Recursion"] : Localizer["ConvertRecursively"])</span>
                    </div>
                    <Button Text="@Localizer[Parent.CurrentView == AssetExplorerView.GameView ? "Export" : "Convert"]" Icon="file_download" OnClickAsync="Export"/>
                </div>
                @if (Parent.CurrentView == AssetExplorerView.GameView)
                {
                    <div class="pl-6" style="min-width: 400px;">
                        <div class="row">
                            <label>@Localizer["ExportFormats"]</label>
                            <div class="flex-grow"></div>
                            <Button Text="@Localizer["SelectAll"]" CssClass="mr-3" OnClick="SelectAllFormats"/>
                            <Button Text="@Localizer["DeselectAll"]" OnClick="DeselectAllFormats"/>
                        </div>
                        <div class="flex flex-row flex-wrap mt-2 mb-4 bg-grey-900 border border-grey-600 px-3 py-2">
                            @foreach (var extension in Extensions)
                            {
                                <div class="row my-1" style="flex: 0 0 170px;">
                                    <Checkbox IsChecked="@(ExportContext.ExportFormats[extension])" OnChange="@(b => { ExportContext.ExportFormats[extension] = b; StateHasChanged();})"/>
                                    <span class="ml-2 mr-4">@extension</span>
                                </div>
                            }
                        </div>
                    </div>
                }
            </div>
        </div>
    </BodyView>
</AutosizeModal>

@code
{
    [CascadingParameter]
    public IAssetExplorerParent Parent { get; set; }

    [CascadingParameter(Name = "AssetExplorer")]
    public AssetExplorer AssetExplorer { get; set; }

    private AutosizeModal Modal { get; set; }
    private IAssetExplorerNode ContextNode { get; set; }
    private ExportContext ExportContext { get; set; }
    private IEnumerable<string> Extensions { get; set; }

    protected override void OnInitialized()
    {
        ExportContext = new ExportContext(ExportContextPreset.None, Profile.Current.Type);
        Extensions = Parent.CurrentView == AssetExplorerView.FileSystem
            ? new[] {"btex", "exml"}
            : ArchiveHelper.RelativeExtensions(Profile.Current.Type);
    }

    public void SetContextNode(IAssetExplorerNode node)
    {
        ContextNode = node;
    }

    public void Open()
    {
        AppState.IsModalOpen = true;
        WpfService.Set3DViewportVisibility(false);
        Modal.Open();
    }

    private void Close()
    {
        Modal.Close();
        AppState.IsModalOpen = false;

        if (AppState.Is3DViewerOpen)
        {
            WpfService.Set3DViewportVisibility(true);
        }
    }

    private void ChangePreset(ExportContextPreset preset)
    {
        ExportContext = new ExportContext(preset, Profile.Current.Type);
        StateHasChanged();
    }

    private void SelectAllFormats()
    {
        foreach (var key in ExportContext.ExportFormats.Keys)
        {
            ExportContext.ExportFormats[key] = true;
            StateHasChanged();
        }
    }

    private void DeselectAllFormats()
    {
        foreach (var key in ExportContext.ExportFormats.Keys)
        {
            ExportContext.ExportFormats[key] = false;
            StateHasChanged();
        }
    }

    private async Task Export()
    {
        if (Parent.CurrentView == AssetExplorerView.GameView)
        {
            await WpfService.OpenFolderDialogAsync(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), folder => { Task.Run(() => InvokeAsync(() => DoExport(folder))); });
        }
        else
        {
            await Task.Run(() => InvokeAsync(DoExportFileSystem));
        }
    }

    private void DoExportFileSystem()
    {
        Close();
        AssetExplorer.SetLoading(true, "Exporting");

        ContextNode.Traverse(node =>
        {
            if (node.Type != ExplorerItemType.Directory)
            {
                var data = node.Data;

                if (ExportContext.ExportFormats["btex"] && node.Type == ExplorerItemType.Texture)
                {
                    var converter = new TextureConverter(Profile.Current.Type);
                    foreach (var (extension, _) in ExportContext.TextureFormats.Where(f => f.Value))
                    {
                        var textureData = extension switch
                        {
                            "png" => converter.BtexToPng(data),
                            "tga" => converter.BtexToTga(data),
                            "dds" => converter.BtexToDds(data),
                            _ => data
                            };

                        File.WriteAllBytes(node.Path.Replace(".btex", $".{extension}"), textureData);
                    }
                }

                if (ExportContext.ExportFormats["exml"] && node.Type == ExplorerItemType.Xml)
                {
                    foreach (var (extension, _) in ExportContext.ScriptFormats.Where(f => f.Value))
                    {
                        if (extension == "xml")
                        {
                            var builder = new StringBuilder();
                            Xmb2Document.Dump(data, builder);
                            File.WriteAllText(node.Path.Replace(".exml", $".{extension}"), builder.ToString());
                        }
                        else
                        {
                            File.WriteAllBytes(node.Path.Replace(".exml", $".{extension}"), data);
                        }
                    }
                }
            }
        });

        AssetExplorer.SetLoading(false);
    }

    private void DoExport(string folder)
    {
        Close();
        AssetExplorer.SetLoading(true, "Exporting");

        var files = new Dictionary<string, ConcurrentBag<string>>();
        var paths = AppState.PathMap;

        if (ExportContext.Recursive)
        {
            void Traverse(IAssetExplorerNode node, Action<IAssetExplorerNode> visitor)
            {
                visitor(node);
                Parallel.ForEach(((AssetExplorerNode)node).ChildNodes, child => { Traverse(child, visitor); });
            }

            Traverse(ContextNode, nodeBase =>
            {
                var node = (AssetExplorerNode)nodeBase;

                if (!node.ChildNodes.Any())
                {
                    if (ExportContext.ExportFormats[UriHelper.GetTrueExtensionFromFileName(node.Name)])
                    {
                        var uri = node.Path;
                        var location = paths[Cryptography.HashFileUri64(uri)];

                        ConcurrentBag<string> uris;
                        lock (files)
                        {
                            if (!files.TryGetValue(location, out uris))
                            {
                                uris = new ConcurrentBag<string>();
                                files.Add(location, uris);
                            }
                        }

                        uris.Add(uri);
                    }
                }
            });
        }
        else
        {
            foreach (var node in ContextNode.Children)
            {
                if (!node.HasChildren)
                {
                    if (ExportContext.ExportFormats[UriHelper.GetTrueExtensionFromFileName(node.Name)])
                    {
                        var location = paths[Cryptography.HashFileUri64(node.Path)];
                        if (!files.TryGetValue(location, out var uris))
                        {
                            uris = new ConcurrentBag<string> {node.Path};
                            files.TryAdd(location, uris);
                        }
                        else
                        {
                            uris.Add(node.Path);
                        }
                    }
                }
            }
        }

        var baseUri = ContextNode.Path;
        var basePath = ContextNode.Name.Replace(":", "") + "\\";
        var converter = new TextureConverter(Profile.Current.Type);
        var texturesToProcess = new ConcurrentBag<Action>();
        var done = false;

        Parallel.Invoke(() =>
        {
    // This ensures textures are not multithreaded so DirectXTex doesn't crash
            while (!done || texturesToProcess.Any())
            {
                if (!texturesToProcess.Any())
                {
                    Task.Delay(50).Wait();
                    continue;
                }

                texturesToProcess.TryTake(out var next);
                next?.Invoke();
            }
        }, () =>
        {
            Parallel.ForEach(files, kvp =>
            {
                var (archiveLocation, uris) = kvp;
                using var unpacker = new EbonyArchive($@"{Context.Profile.GameDataDirectory}\{archiveLocation}");

    // No point adding parallelism here as the unpacker shares the same filestream anyway
                foreach (var uri in uris)
                {
                    var data = unpacker[uri].GetReadableData();
                    var type = AssetExplorerItem.GetType(uri);

                    var relativePath = baseUri.Length > 0
                        ? basePath + uri
                            .Replace(baseUri, "")
                            .Replace("://", "/")
                            .Replace('/', '\\')
                        : basePath + uri
                            .Replace("://", "/")
                            .Replace('/', '\\');

                    var currentDirectory = folder;
                    var split = relativePath.Split('\\');
                    for (var index = 0; index < split.Length - 1; index++)
                    {
                        var token = split[index];
                        currentDirectory += "\\" + token;
                        if (!Directory.Exists(currentDirectory))
                        {
                            Directory.CreateDirectory(currentDirectory);
                        }
                    }

                    var absolutePath = currentDirectory + "\\" + UriHelper.ReplaceFileNameExtensionWithTrueExtension(split.Last());

                    switch (type)
                    {
                        case ExplorerItemType.Texture:
                        {
                            foreach (var (extension, _) in ExportContext.TextureFormats.Where(f => f.Value))
                            {
                                var finalAbsolutePath = absolutePath[..absolutePath.LastIndexOf('.')] + "." + extension.ToLower();
                                texturesToProcess.Add(() =>
                                {
                                    converter.ExportTexture(finalAbsolutePath, (ImageFormat)extension, data);
                                });
                            }
                            break;
                        }
                        case ExplorerItemType.Xml:
                        {
                            foreach (var (extension, _) in ExportContext.ScriptFormats.Where(f => f.Value))
                            {
                                absolutePath = absolutePath[..absolutePath.LastIndexOf('.')] + "." + extension.ToLower();
                                var scriptData = extension switch
                                {
                                    "xml" => Xmb2Document.ToXml(data),
                                    _ => data
                                    };

                                File.WriteAllBytes(absolutePath, scriptData);
                            }
                            break;
                        }
                        case ExplorerItemType.Unspecified:
                        case ExplorerItemType.Unsupported:
                        case ExplorerItemType.Directory:
                        case ExplorerItemType.Material:
                        case ExplorerItemType.Model:
                        case ExplorerItemType.Text:
                        case ExplorerItemType.AnimationPackage:
                        default:
                            File.WriteAllBytes(absolutePath, data);
                            break;
                    }
                }
            });

            done = true;
        });

        AssetExplorer.SetLoading(false);
    }
}